package de.fhhof.robot.control.net;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import de.fhhof.robot.control.IMessage;
import de.fhhof.robot.control.Message;
import de.fhhof.robot.control.common.CommonConstants;



public class Connection implements IConnection, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private String senderId;
	
	private String receiverId;

    private Socket client;
    private DataInputStream in;
    private DataOutputStream out;
    private int type = 0;
    private int port =9999;
    private String host;
    private ServerSocket serverSkt;
    private String key  = null;
    private IMessage lastMessage = null;
    
	public Connection(int serverPort,String senderId,String receiverId){
		try{
			this.key = serverPort+"-"+ senderId+"-"+receiverId;
			this.senderId = senderId;
			this.receiverId = receiverId;
			this.type = 1;
			this.port = serverPort;
			this.serverSkt = new ServerSocket(serverPort);		
		}
		catch(Exception ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			//need to throw from here as the socket port is already binding with another app. 
		}
		/** For server connection we have to wait for client to get connecting 
		 * After the client has successfully connected the we can receive data.
		 * */

		try{
			System.out.println("Waiting for connecting client: "+ this.receiverId);
			//serverSkt.setSoTimeout(CommonConstants.SOCKET_TIMEOUT_PERIOD);
			client = serverSkt.accept();
			//client.setSoTimeout(CommonConstants.SOCKET_TIMEOUT_PERIOD); ///////////
			System.out.println("Connected to client :"+ client.getInetAddress());
			this.initStreams();
		}
		catch(IOException ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			this.reconnect();
		}
		catch(Exception ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
		}
		

				
		
	}
	public Connection(String host,int port,String senderId,String receiverId){
		try{
			this.key = host+ "-"+port+"-"+ senderId+"-"+receiverId;
			this.senderId = senderId;
			this.receiverId = receiverId;
			this.type = 0;
			this.port = port;
			this.host = host;
			this.client = new Socket(host,port);
			System.out.println("Connected to server: "+ host+" on port: "+port);
			this.initStreams();
					
		}
		catch(UnknownHostException ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			this.reconnect();
		}
		catch(ConnectException ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			this.reconnect();
		}
		catch(NoRouteToHostException ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			this.reconnect();
		}		
		catch(IOException ex){
			//ex.printStackTrace();
			System.out.println("Connection problem: "+ex.getMessage());
			this.reconnect();
		}
		
		

	}
	private void initStreams()throws IOException{
		in = new DataInputStream(client.getInputStream());
		out = new DataOutputStream(client.getOutputStream());				
	}

	private void reconnectToServer(){
		Socket socket = null;

		while( !Thread.currentThread().isInterrupted()){
			try{
				Thread.sleep(CommonConstants.SOCKET_RECONNECT_INTERVAL);
				System.out.println("Connecting to host:"+ host +" on port: "+port);
				socket = new Socket(host,port);
				System.out.println("Connected to server: "+ host +" on port: "+port);
				
				if(socket != null){
					this.client= socket;
					this.initStreams();
					break;
				}
			}
			catch(Exception ex){
				
			}
		}
				
	}
	private void reconnectToClient(){
		Socket socket = null;
		while( !Thread.currentThread().isInterrupted()){
			try{
				Thread.sleep(CommonConstants.SOCKET_RECONNECT_INTERVAL);
				System.out.println("Waiting for connection on port: "+port);
				socket = this.serverSkt.accept();
				System.out.println("Connected to client :"+ client.getInetAddress());
				if(socket != null){
					
					this.client= socket;
					//this.client.setSoTimeout(CommonConstants.CommonConstants.SOCKET_TIMEOUT_PERIOD); ///////////
					this.initStreams();
					break;
				}
			}
			catch(Exception ex){
				
			}
		}
					
	}
	public IMessage receive(){
	
		IMessage msg = null;
		
		boolean received = false;
		while(!Thread.currentThread().isInterrupted() && !received){
			try{
				byte[] header = new byte[Message.MESSAGE_HEADER_LENGTH];
				in.readFully(header);
				String msgHeader = new String(header);
						
				String payLoadSize = msgHeader.substring(21);
				int payLoadLength = Integer.parseInt(payLoadSize);
				
				byte[] body = new byte[payLoadLength];
				in.readFully(body);
				
				msg = new Message();
				msg.setConnection(this.key);
				msg.setHeader(msgHeader);
				msg.setPayload(new String(body));
				msg.parse();	
				received = true;
			}
			catch(SocketTimeoutException ex){
				if(this.type == 0){
					this.reconnect();
				}
				else if(this.type == 1){
					System.out.println("Socket read time out: no reply from peer. "+ ex.getMessage());
					this.reconnect();
				}				
			
			}
			catch(IOException ex){

				if(this.type == 0){
					this.reconnect();
				}
				else if(this.type == 1){
					System.out.println("Connection to peer is lost '"+ ex.getMessage() +"' trying to re-establish connection");
					//this.reSendMessage(this.lastMessage);
					this.reconnect();

				}
			}
		}
	
		return msg;		
	}
	private void reSendMessage(IMessage msg){
		if(msg != null){
			this.send(msg);
		}
		
	}
	
	public void send(IMessage message){

		message.setConnection(this.key);
		
		byte[] rawMessage = ((Message) message).getRawMessage();
		boolean sent = false;
		while(!Thread.currentThread().isInterrupted() && !sent){
			try{
				out.write(rawMessage);
				out.write(message.getPayload().getBytes());
				sent = true;
				this.lastMessage = message;
			}
			catch(SocketException ex){
				//ex.printStackTrace();
				System.out.println("Network error: "+ex.getMessage());
				this.reconnect();
			}
			catch(IOException ex){
				ex.printStackTrace();
				System.out.println("Network error: "+ex.getMessage());
				this.reconnect();
			}
		}

	}
	private void reconnect(){
		if(this.type == 0){
			this.reconnectToServer();	
		}
		else{
			this.reconnectToClient();
		}		
	}

	@Override
	public void waitForClient() {
		try{
			client = serverSkt.accept();
			in = new DataInputStream(client.getInputStream());
			out = new DataOutputStream(client.getOutputStream());	
		}
		catch(Exception ex){
			ex.printStackTrace();
		}
		
	}
	@Override
	public String getSenderId() {
		return this.senderId;
	}
	@Override
	public String getReceiverId() {
		return this.receiverId;
	}


}
